#ifndef __M_BUUFER_H__
#define __M_BUUFER_H__

/*
缓冲区的设计思想：
    使用队列（即先进先出），缓存日志消息，逐条处理
    不推荐使用链表，因为使用链表的话会频繁的创建和删除结点（进一条日志创建一个结点，出一条日志删除一个结点）
    我们可以设计一个环形队列，提前将空间申请好，对空间进行循环利用
*/

/*
    缓冲区的操作会涉及到多线程，因此一定要保证线程安全
    线程安全的实现：对于缓冲区的读写进行加锁
    因为写日志操作在实际的开发中不会分配太多资源，所以写日志的工作线程一个日志器只需要有一个即可
    涉及到的锁冲突：生产者与生产者的互斥  &&  生产者用消费者的互斥
*/    

/*
    如何再次提高效率？环形队列的锁冲突较为严重，因为所有的线程之间都存在互斥关系
    解决方案：双缓冲区
        我们申请两个缓冲区，一个“写入日志缓冲区”，一个“处理日志缓冲区”
        业务线程将日志写入“写入日志缓冲区”，工作线程从“处理日志缓冲区”取出数据进行写磁盘的操作
        当“处理日志缓冲区”的任务处理完毕，将两个缓冲区进行交换即可
        这样减少了生产者与消费者的锁冲突（只有在交换的时候，生产者与消费者才会产生一次锁冲突）
*/

/*
    缓冲区放的是格式化后的日子消息字符串，而不是一个个 LogMsg 对象
    因为会涉及到频繁的拷贝，放 LogMsg 对象会就要频繁的调用其构造
    并且字符串的话可以对缓冲区内容进行一次性IO操作，减少IO次数，提高效率
*/

#include <vector>
#include <algorithm> 
#include <cassert>

namespace MyLog
{
    #define BUFFER_DEFAULT_SIZE (1*1024*1024)  //缓冲区默认大小
    #define BUFFER_INCREMENT_SIZE (1*1024*1024)  //每次线性扩容增长大小
    #define BUFFER_THRESHOLD_SIZE (10*1024*1024)  //阈值 先两倍扩容，达到阈值后线性扩容
    class Buffer
    {
    public:
        Buffer() :_buffer(BUFFER_DEFAULT_SIZE) {}
        //向缓冲区写入数据

        void push(const char* data, size_t len)
        {
            //判断是否有足够空间写入
            if(len > writeAbleSize())
            {
                //扩容
                //注：无限扩容是不安全的行为，这里是否扩容由外层 AsyncLooper 类 控制
                size_t nextSize;
                //这里三目一定要加（），否则就是先算 + , 然后前两个相加的结果与_buffer.size()判断
                if(_buffer.size() < BUFFER_THRESHOLD_SIZE)
                    nextSize = _buffer.size() + (len > _buffer.size() ? len : _buffer.size());
                else
                    nextSize = _buffer.size() + (len > BUFFER_INCREMENT_SIZE ? len : BUFFER_INCREMENT_SIZE);
                _buffer.resize(nextSize);
            }
            assert(_writeIdx + len <= _buffer.size());
            std::copy(data, data + len, &_buffer[_writeIdx]);
            _writeIdx += len;
        }

        //返回可写数据长度
        size_t writeAbleSize()
        {
            return _buffer.size() - _writeIdx;
        }

        //重置读写位置，初始化缓冲区
        void reset()
        {
            _writeIdx = _readIdx = 0;
        }

        //交换缓冲区
        void swap(Buffer& buffer)
        {
            _buffer.swap(buffer._buffer);
            std::swap(_writeIdx, buffer._writeIdx);
            std::swap(_readIdx, buffer._readIdx);
        }

        //判断缓冲区是否为空
        bool empty()
        {
            return _readIdx == _writeIdx;
        }
   
    public:
        //为什么不直接提供一个接口返回读取的数据，而是要提供这三个接口
        //因为返回要读的数据意味着要多一次拷贝！！！
        //从缓冲区中拷贝到工作线程的缓冲区，再由工作线程落地

        //返回可读数据的起始地址
        const char* readBegin()
        {
            assert(_readIdx < _buffer.size());
            return &_buffer[_readIdx];
        }

        //返回可读数据的长度
        size_t readAbleSize()
        {
            return _writeIdx - _readIdx;
        }

        //移动读指针
        void moveReader(size_t len)
        {
             assert(len <= readAbleSize());
            _readIdx += len;
        }

    private:
        //这里没用string，因为 string 碰到换行等特殊字符是不处理的
        std::vector<char> _buffer; 
        size_t _writeIdx = 0;
        size_t _readIdx = 0;
    };
}

#endif